﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Consul;
using Microsoft.Extensions.Options;

namespace Extensions.Consul;

public class ConsulServerManager:IConsulServerManager
{
    private readonly IConsulClient _consulClient;
    private readonly ConsulOption _consulOption;
    private ConcurrentDictionary<string, int> _serverCalls = new ConcurrentDictionary<string, int>();
    public ConsulServerManager(IConsulClient consulClient,IOptions<ConsulOption> options)
    {
        _consulClient = consulClient?? throw new ArgumentNullException(nameof(consulClient));
        _consulOption = options?.Value ?? throw new ArgumentNullException(nameof(options));
    }
    /// <summary>
    /// 获取服务地址
    /// </summary>
    /// <param name="serviceName"></param>
    /// <returns></returns>
    /// <exception cref="System.NotImplementedException"></exception>
    public async Task<string> GetServerAddressAsync(string serviceName)
    {
        var service = await GetAgentServiceAsync(serviceName);
        return $"http://{service.Address}:{service.Port}";
    }
    /// <summary>
    /// 根据负载均衡策略获取服务信息
    /// </summary>
    /// <param name="serviceName"></param>
    /// <returns></returns>
    /// <exception cref="System.NotImplementedException"></exception>
    public async Task<AgentService> GetAgentServiceAsync(string serviceName)
    {
        var services = await GetServerListAsync(serviceName);
        // 按照负载均衡策略返回 服务地址
        return BalancingRoute(services, serviceName);
    }
    /// <summary>
    /// 获取服务列表
    /// </summary>
    /// <param name="serviceName"></param>
    /// <returns></returns>
    /// <exception cref="System.NotImplementedException"></exception>
    public async Task<IList<AgentService>> GetServerListAsync(string serviceName)
    {
        var result = await _consulClient.Health.Service(serviceName);
        if (result.StatusCode!=HttpStatusCode.OK)
        {
            throw new ConsulRequestException("获取服务信息失败！", result.StatusCode);
        }

        return result.Response.Select(p => p.Service).ToList();
    }


    private AgentService BalancingRoute(IList<AgentService> services, string key)
    {
        if (services == null || !services.Any())
        {
            throw new ArgumentNullException(nameof(services), $"当前未找到{key}可用服务！");
        }

        switch (_consulOption.LBStrategy)
        {
            case "First":
                return First(services);
            case "Random":
                return Random(services);
            case "RoundRobin":
                return RoundRobin(services, key);
            case "WeightRandom":
                return WeightRandom(services);
            case "WeightRoundRobin":
                return WeightRoundRobin(services, key);
            default:
                return RoundRobin(services, key);
        }
    }

    /// <summary>
    /// 第一个
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    private AgentService First(IList<AgentService> services)
    {
        return services.First();
    }

    /// <summary>
    /// 随机
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    private AgentService Random(IList<AgentService> services)
    {
        return services.ElementAt(new Random().Next(0, services.Count()));
    }

    /// <summary>
    /// 轮询
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    private AgentService RoundRobin(IList<AgentService> services, string key)
    {
        var count = _serverCalls.GetOrAdd(key, 0);
        var service = services.ElementAt(count++ % services.Count());
        _serverCalls[key] = count;
        return service;
    }

    /// <summary>
    /// 加权随机
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public AgentService WeightRandom(IList<AgentService> services)
    {
        var pairs = services.SelectMany(s =>
        {
            var weight = 1;
            if (s.Meta.ContainsKey("Weight") && int.TryParse(s.Meta["Weight"], out int w))
            {
                weight = w;
            }
            var result = new List<AgentService>();
            for (int i = 0; i < weight; i++)
            {
                result.Add(s);
            }
            return result;
        }).ToList();
        return Random(pairs);
    }

    /// <summary>
    /// 加权轮询
    /// </summary>
    /// <param name="services"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    public AgentService WeightRoundRobin(IList<AgentService> services, string key)
    {
        var pairs = services.SelectMany(s =>
        {
            var weight = 1;
            if (s.Meta.ContainsKey("Weight") && int.TryParse(s.Meta["Weight"], out int w))
            {
                weight = w;
            }
            var result = new List<AgentService>();
            for (int i = 0; i < weight; i++)
            {
                result.Add(s);
            }
            return result;
        }).ToList();
        return RoundRobin(pairs, key);
    }
}